#include "../pch.h"
#include "CharUnicodeInfo.h"
#include <limits.h>

namespace BFX
{

typedef	StringTraitsBase<UCHAR>	StrTraits16;

//////////////////////////////////////////////////////////////////////////////

UniString::UniString()
	: m_pStringData(new StringData())
{
}

// Creates a new string with the characters copied in from utf-16 characters in a subarray,
// Or entire zero-ended string if given length is -1.
UniString::UniString(UCSTR pszSrc, int nLength)
{
	BFX_REQUIRE (nLength >= -1);

	nLength = pszSrc == NULL ? 0 : (nLength == -1 ? StrTraits16::StringLength(pszSrc) : nLength);
	m_pStringData = new StringData(nLength);
	if (nLength > 0)
	{
		memcpy(m_pStringData->m_pBuffer, pszSrc, nLength * sizeof(UCHAR));
		SetLength(nLength);
	}
}

UniString::UniString(const BYTE* pBytes, int nLength, PCSTR pszEnc)
{
	REF<REFAutoreleasePool> pCurPool = REFAutoreleasePool::CreateOrGetCurrentPool();

	REF<REFEncoding> pEnc = REFEncoding::GetEncoding(pszEnc);
	BFX_ASSERT(pEnc != NULL);

	int nCharCount = pEnc->GetCharCount(pBytes, nLength);
	if (nCharCount > 0)
	{
		SequenceT<UCHAR> chars;
		pEnc->GetChars(pBytes, nLength, chars.GetBuffer(nCharCount + 1), nCharCount);
		chars.ReleaseBuffer(nCharCount + 1);
		chars[nCharCount] = 0;	// '\0'

		(*this) = chars.GetData();
	}
	else
		m_pStringData = new StringData();
}

UniString::UniString(const SequenceT<BYTE> bytes, PCSTR pszEnc)
{
	REF<REFAutoreleasePool> pCurPool = REFAutoreleasePool::CreateOrGetCurrentPool();

	REF<REFEncoding> pEnc = REFEncoding::GetEncoding(pszEnc);
	BFX_ASSERT(pEnc != NULL);

	int nCharCount = pEnc->GetCharCount(bytes.GetData(), bytes.GetSize());
	if (nCharCount > 0)
	{
		SequenceT<UCHAR> chars;
		pEnc->GetChars(bytes.GetData(), bytes.GetSize(), chars.GetBuffer(nCharCount + 1), nCharCount);
		chars.ReleaseBuffer(nCharCount + 1);
		chars[nCharCount] = 0;	// '\0'

		(*this) = chars.GetData();
	}
	else
		m_pStringData = new StringData();
}

UniString::UniString(const UniString& str)
{
	m_pStringData = str.m_pStringData;
}

UniString::~UniString()
{
}

int UniString::GetLength() const
{
	return (m_pStringData->m_nLength);
}

bool UniString::IsEmpty() const
{
	return (GetLength() == 0);
}

void UniString::SetEmpty()
{
	m_pStringData = new StringData();
}

UCHAR UniString::GetAt(int iIndex) const
{
	BFX_REQUIRE(iIndex >= 0 && iIndex < m_pStringData->m_nLength);
	return m_pStringData->m_pBuffer[iIndex];
}

SequenceT<BYTE> UniString::GetBytes(PCSTR pszEnc) const
{
	// ensure we have autorelease pool installed.
	REF<REFAutoreleasePool> pCurPool = REFAutoreleasePool::CreateOrGetCurrentPool();

	REF<REFEncoding> pEnc = REFEncoding::GetEncoding(pszEnc);
	BFX_ASSERT(pEnc != NULL);

	SequenceT<BYTE> bytes;
	UCHAR* pChars = m_pStringData->m_pBuffer;
	int nCharCount = m_pStringData->m_nLength;
	int nByteCount = pEnc->GetByteCount(pChars, nCharCount);
	if (nByteCount > 0)
	{
		pEnc->GetBytes(pChars, nCharCount, bytes.GetBuffer(nByteCount), nByteCount);
		bytes.ReleaseBuffer(nByteCount);
	}
	return bytes;
}

SequenceT<UCHAR> UniString::GetChars() const
{
	SequenceT<UCHAR> chars;
	UCHAR* pChars = m_pStringData->m_pBuffer;
	int nLength = m_pStringData->m_nLength;
	if (nLength > 0)
	{
		memcpy(chars.GetBuffer(nLength), pChars, nLength * sizeof(UCHAR));
		chars.ReleaseBuffer(nLength);
	}
	return chars;
}

UCSTR UniString::GetData() const
{
	return m_pStringData->m_pBuffer;
}

bool UniString::Equals(const UniString& str) const
{
	return (CompareTo(str) == 0);
}

int UniString::CompareTo(const UniString& str, bool bIgnoreCase) const
{
	// Optimized comparison.
	if ((IsEmpty() && str.IsEmpty()) || (m_pStringData == str.m_pStringData))
		return 0;

	UCSTR pszStr1 = GetData();
	UCSTR pszStr2 = str.GetData();
	return (!bIgnoreCase) ? (StrTraits16::StringCompare(pszStr1, pszStr2)) :
		(StrTraits16::StringCompareIgnore(pszStr1, pszStr2));
}

bool UniString::StartsWith(const UniString& strPrefix) const
{
	if (this == &strPrefix)
		return true;
	int nCount = strPrefix.GetLength();
	if (nCount == 0)
		return true;
	if (GetLength() < nCount)
		return false;

	UCSTR pszA = GetData();
	UCSTR pszB = strPrefix.GetData();
	for (int i = 0; i < nCount; i ++)
	{
		if (pszA[i] != pszB[i])
			return false;
	}

	return true;
}

bool UniString::EndsWith(const UniString& strSuffix) const
{
	if (this == &strSuffix)
		return true;
	int nCount = strSuffix.GetLength();
	if (nCount == 0)
		return true;
	if (GetLength() < nCount)
		return false;

	UCSTR pszA = GetData() + (GetLength() - nCount);
	UCSTR pszB = strSuffix.GetData();
	for (int i = 0; i < nCount; i ++)
	{
		if (pszA[i] != pszB[i])
			return false;
	}

	return true;
}

int UniString::IndexOf(UCHAR ch) const
{
	UCSTR pszBlock = GetData();
	UCSTR pch = StrTraits16::StringFindChar(pszBlock, ch);
	return (int)((pch == NULL) ? -1 : (pch - pszBlock));
}

int UniString::IndexOf(const UniString& str) const
{
	if (str.GetLength() == 0)
		return -1;
	UCSTR pszSub = str.GetData();
	UCSTR pszBlock = GetData();
	UCSTR pch = StrTraits16::StringFindString(pszBlock, pszSub);
	return (int)((pch == NULL) ? -1 : (pch - pszBlock));
}

int UniString::LastIndexOf(UCHAR ch) const
{
	UCSTR pszBlock = GetData();
	UCSTR pch = StrTraits16::StringFindCharRev(pszBlock, ch);
	return (int)((pch == NULL) ? -1 : (pch - pszBlock));
}

int UniString::LastIndexOf(const UniString& str) const
{
	UCSTR pszSub = str.GetData();
	UCSTR pszBlock = GetData();
	UCSTR pch = StrTraits16::StringFindStringRev(pszBlock, pszSub);
	return (int)((pch == NULL) ? -1 : (pch - pszBlock));
}

UniString UniString::Trim() const
{
	int iStart = 0;
	int iEnd = GetLength() - 1;
	UCSTR pszBlock = GetData();
	// lead chars.
	while (iStart < GetLength())
	{
		if (!CharUnicodeInfo::IsWhiteSpace(pszBlock[iStart]))
			break;
		iStart++;
	}
	// tail chars.
	while (iStart < iEnd)
	{
		if (!CharUnicodeInfo::IsWhiteSpace(pszBlock[iEnd]))
			break;
		iEnd--;
	}
	int nLength = iEnd - iStart + 1;
	return ((iStart > 0) || (iEnd < GetLength())) ? Substring(iStart, nLength) : (*this);
}

UniString UniString::ToLowercase() const
{
	UniString strResult;
	int nStrLen = GetLength();
	CharUnicodeInfo::ToLowercase(GetData(), strResult.GetBuffer(nStrLen), nStrLen);
	strResult.SetLength(nStrLen);
	return strResult;
}

UniString UniString::ToUppercase() const
{
	UniString strResult;
	int nStrLen = GetLength();
	CharUnicodeInfo::ToUppercase(GetData(), strResult.GetBuffer(nStrLen), nStrLen);
	strResult.SetLength(nStrLen);
	return strResult;
}

UniString UniString::Substring(int iOffset) const
{
	return Substring(iOffset, GetLength() - iOffset);
}

UniString UniString::Substring(int iOffset, int nLength) const
{
	BFX_ASSERT1((iOffset >= 0 && iOffset <= GetLength()), "iOffset is out of range!");
	BFX_ASSERT1((nLength >= 0 && iOffset <= GetLength() - nLength), "nLength is out of range!");

	if (iOffset == 0 && nLength == GetLength())
	{
		return (*this);
	}
	UniString strResult;
	memcpy(strResult.GetBuffer(nLength), GetData() + iOffset, nLength * sizeof(UCHAR));
	strResult.SetLength(nLength);

	return strResult;
}

UniString UniString::Replace(UCHAR chTarget, UCHAR chReplacement) const
{
	UniString strResult(*this);

	if (chTarget != chReplacement)
	{
		int nLength = strResult.GetLength();
		UCHAR* pszBuffer = strResult.GetBuffer(nLength);
		for (int iChar = 0; iChar < nLength; iChar++)
		{
			if (pszBuffer[iChar] == chTarget)
			{
				pszBuffer[iChar] = chReplacement;
			}
		}
		strResult.SetLength(nLength);
	}
	return (strResult);
}

UniString UniString::Replace(const UniString& strTarget, const UniString& strReplacement) const
{

	int nSourceLen = strTarget.GetLength();
	BFX_ENSURE(nSourceLen > 0);
	UCSTR pszOld = strTarget.GetData();
	int nReplacementLen = strReplacement.GetLength();
	UCSTR pszNew = strTarget.GetData();

	// loop once to figure out the size of the result string
	UniString strResult(*this);
	int nCount = 0;
	UCSTR pszStart = strResult.GetData();
	UCSTR pszEnd = pszStart + GetLength();
	while (pszStart < pszEnd)
	{
		UCSTR pszTarget;
		while ((pszTarget = StrTraits16::StringFindString(pszStart, pszOld)) != NULL)
		{
			nCount++;
			pszStart = pszTarget + nSourceLen;
		}
		pszStart += StrTraits16::StringLength(pszStart) + 1;
	}

	// if any changes were made, make them
	if (nCount > 0)
	{
		// if the buffer is too small, just allocate a new buffer (slow but sure)
		int nOldLength = strResult.GetLength();
		int nNewLength = nOldLength + (nReplacementLen - nSourceLen) * nCount;

		UCHAR* pszBuffer = strResult.GetBuffer(__MAX(nNewLength, nOldLength));
		pszStart = pszBuffer;
		pszEnd = pszStart + nOldLength;

		// loop again to actually do the work
		while (pszStart < pszEnd)
		{
			UCHAR* pszTarget;
			while ((pszTarget = (UCHAR*) StrTraits16::StringFindString(pszStart, pszOld)) != NULL)
			{
				int nBalance = nOldLength - int(pszTarget - pszBuffer + nSourceLen);
				memmove(pszTarget + nReplacementLen, pszTarget + nSourceLen,
					nBalance * sizeof(UCHAR));
				memcpy(pszTarget, pszNew, nReplacementLen * sizeof(UCHAR));
				pszStart = pszTarget + nReplacementLen;
				pszTarget[nReplacementLen + nBalance] = 0;
				nOldLength += (nReplacementLen - nSourceLen);
			}
			pszStart += StrTraits16::StringLength(pszStart) + 1;
		}
		BFX_ENSURE(pszBuffer[nNewLength] == 0);
		strResult.SetLength(nNewLength);
	}

	return (strResult);
}

void UniString::Append(const UniString& strSrc)
{
	if (IsEmpty())
		m_pStringData = strSrc.m_pStringData;
	else
		Append(strSrc, strSrc.GetLength());
}

void UniString::Append(UCHAR chSrc)
{
	Append(&chSrc, 1);
}

void UniString::Append(UCSTR pszSrc, int nLength)
{
	BFX_REQUIRE (nLength >= 0);
	BFX_REQUIRE (pszSrc != NULL || nLength == 0);

	UINT64 iOffset = pszSrc - GetData();
	int nOldLength = GetLength();

	BFX_ASSERT (nOldLength >= 0);	// underflow???
	BFX_ENSURE (INT_MAX - nLength >= nOldLength);

	int nNewLength = nOldLength + nLength;
	UCHAR* pszBuffer = GetBuffer(nNewLength);

	if (iOffset <= (UINT64)nOldLength)
	{
		// since the destination is beyond the end of the original buffer.
		pszSrc = pszBuffer + iOffset;
	}
	memmove(pszBuffer + nOldLength, pszSrc, nLength * sizeof(UCHAR));
	SetLength(nNewLength);
}

UCHAR* UniString::GetBuffer(int nLength)
{
	if (m_pStringData->GetRefCount() > 1)
	{
		// clone it.
		int nOldLength = GetLength();
		int nAllocLength = __MAX(nOldLength, nLength);
		StringData* pNewData = new StringData(nAllocLength);
		if (nOldLength > 0)
		{
			memcpy(pNewData->m_pBuffer, GetData(), nOldLength * sizeof(UCHAR));
			pNewData->m_pBuffer[nOldLength] = 0;
			pNewData->m_nLength = nOldLength;
		}
		m_pStringData = pNewData;
	}
	else
	{
		// grow it up if necessary.
		m_pStringData->Capacity(nLength);
	}

	return m_pStringData->m_pBuffer;
}

void UniString::SetLength(int nLength)
{
	BFX_ASSERT(nLength >= 0);
	BFX_ASSERT(nLength <= m_pStringData->m_nAllocLength);
	BFX_ASSERT(m_pStringData->GetRefCount() == 1);

	m_pStringData->m_nLength = nLength;
	m_pStringData->m_pBuffer[nLength] = 0;
}

UniString& UniString::operator=(const UniString& strSrc)
{
	m_pStringData = strSrc.m_pStringData;
	return (*this);
}

UniString& UniString::operator+=(const UniString& strSrc)
{
	Append(strSrc, strSrc.GetLength());
	return (*this);
}

UniString& UniString::operator+=(UCSTR pszSrc)
{
	Append(pszSrc, StrTraits16::StringLength(pszSrc));
	return (*this);
}

UniString& UniString::operator+=(UCHAR chSrc)
{
	Append(&chSrc, 1);
	return (*this);
}

bool operator==(const UniString& str1, const UniString& str2) throw()
{
	// Optimized comparison.
	if (str1.GetLength() != str2.GetLength())
		return false;
	return (str1.CompareTo(str2) == 0);
}

bool operator!=(const UniString& str1, const UniString& str2) throw()
{
	return !(str1 == str2);
}

UniString operator+(const UniString& str1, const UniString& str2) throw()
{
	UniString strResult(str1);
	strResult += str2;
	return (strResult);
}

UniString operator+(const UniString& str1, UCSTR psz2) throw()
{
	UniString strResult(str1);
	strResult += psz2;
	return (strResult);
}

UniString operator+(UCSTR psz1, const UniString& str2) throw()
{
	UniString strResult(psz1);
	strResult += str2;
	return (strResult);
}

UniString operator+(const UniString& str1, const UCHAR ch2) throw()
{
	UniString strResult(str1);
	strResult += ch2;
	return (strResult);
}

UniString operator+(UCHAR ch1, const UniString& str2) throw()
{
	UniString strResult;
	strResult += ch1;
	strResult += str2;
	return (strResult);
}

//////////////////////////////////////////////////////////////////////////////

UniString::StringData::StringData(int nCapacity)
{
	m_pBuffer = NULL;
	m_nLength = m_nAllocLength = 0;
	if (nCapacity > 0)
	{
		Capacity(nCapacity);
	}
}
UniString::StringData::~StringData()
{
	if (m_pBuffer != NULL)
		delete[] m_pBuffer;
}

void UniString::StringData::Capacity(int nCapacity)
{
	BFX_ASSERT(nCapacity >= 0);

	// calculate required buffer length, align by m_nAllocLength.
	int nAllocSize = __MAX(m_nAllocLength + 1, 16);
	while (nAllocSize < (nCapacity + 1)) // end with '\0'
	{
		nAllocSize *= 2;
	}
	// if overflow then allocate new.
	if (nAllocSize > (m_nAllocLength + 1))
	{
		UCHAR* pNewBuffer = new UCHAR[nAllocSize];
		if (m_pBuffer != NULL)
		{
			if (m_nLength > 0)
				memcpy(pNewBuffer, m_pBuffer, m_nLength * sizeof(UCHAR));
			delete[] m_pBuffer;
		}
		pNewBuffer[m_nLength] = '\0';
		m_pBuffer = pNewBuffer;
		m_nAllocLength = (nAllocSize - 1);
	}
}

//////////////////////////////////////////////////////////////////////////////
// String conversions.
// NOTE: the bytes of UNICODE character is 2 or 4, depends on target system.

// Returns a UTF-16 string with specified ANSI string.
UniString StringMakeU(const char* pszAnsi)
{
	int nLength = StringTraitsBase<char>::StringLength(pszAnsi);
	return UniString((const BYTE*)pszAnsi, nLength, NULL);
}

// Returns a copy of specified UTF-16 string.
UniString StringMakeU(const UniString& strUtf16)
{
	return strUtf16;
}

// Returns a UTF-16 string with specified UNICODE string.
UniString StringMakeU(const wchar_t* pszUnicode)
{
	int nLength = StringTraitsBase<wchar_t>::StringLength(pszUnicode) * sizeof(wchar_t);
	PCSTR pszEnc = (sizeof(wchar_t) == 2) ? "utf-16" : "utf-32";
	return UniString((const BYTE*)pszUnicode, nLength, pszEnc);
}

// Returns a copy of specified ANSI string.
String StringMakeA(const String& strAnsi)
{
	return strAnsi;
}

// Returns a ANSI string with specified UTF-16 string.
String StringMakeA(UCSTR pszUtf16)
{
	UniString str(pszUtf16);
	SequenceT<BYTE> bytes = str.GetBytes(NULL);
	return String((PCSTR)bytes.GetData(), bytes.GetSize());
}

// Returns a ANSI string with specified UNICODE string.
String StringMakeA(const wchar_t* pszUnicode)
{
	UniString str = StringMakeU(pszUnicode);
	SequenceT<BYTE> bytes = str.GetBytes(NULL);
	return String((PCSTR)bytes.GetData(), bytes.GetSize());
}

// Returns a UNICODE string with specified ANSI string.
StringW StringMakeW(const char* pszAnsi)
{
	UniString str = StringMakeU(pszAnsi);
	int nCharSize = sizeof(wchar_t);
	SequenceT<BYTE> bytes = str.GetBytes(nCharSize == 2 ? "utf-16" : "utf-32");
	BFX_ASSERT((bytes.GetSize() % nCharSize) == 0);
	return StringW((const wchar_t*)bytes.GetData(), bytes.GetSize() / nCharSize);
}

// Returns a UNICODE string with specified UNICODE string.
StringW StringMakeW(UCSTR pszUtf16)
{
	UniString str(pszUtf16);
	int nCharSize = sizeof(wchar_t);
	SequenceT<BYTE> bytes = str.GetBytes(nCharSize == 2 ? "utf-16" : "utf-32");
	BFX_ASSERT((bytes.GetSize() % nCharSize) == 0);
	return StringW((const wchar_t*)bytes.GetData(), bytes.GetSize() / nCharSize);
}

// Returns a copy of specified UNICODE string.
StringW StringMakeW(const StringW& strUnicode)
{
	return strUnicode;
}

}	//	namespace BFX
